﻿using System;
using System.Threading;
using Pdelvo.Minecraft.Protocol.Packets;
using System.Diagnostics;
using java.security;
using Pdelvo.Minecraft.Network;

namespace Pdelvo.Minecraft.Protocol
{
    /// <summary>
    /// 
    /// </summary>
    /// <remarks></remarks>
    public abstract class RemoteInterface : IMinecraftRemoteInterface
    {
        /// <summary>
        /// 
        /// </summary>
        protected Thread Thread;

        /// <summary>
        /// 
        /// </summary>
        private bool _aborting;

        /// <summary>
        /// Initializes a new instance of the <see cref="RemoteInterface"/> class.
        /// </summary>
        /// <param name="endPoint">The end point.</param>
        /// <remarks></remarks>
        protected RemoteInterface(PacketEndPoint endPoint)
        {
            Thread = new Thread(RunLoop);
            EndPoint = endPoint;
            PrepareEndPoint();
        }

        #region IMinecraftRemoteInterface Members

        /// <summary>
        /// Sends the packet.
        /// </summary>
        /// <param name="packet">The packet.</param>
        /// <remarks></remarks>
        public void SendPacket(Packet packet)
        {
            try{
                EndPoint.SendPacket(packet);
            }
            catch (ThreadAbortException)
            {
                if (Aborted != null)
                    Aborted(this, new RemoteInterfaceAbortedEventArgs());
            }
            catch (Exception ex)
            {
                if (Aborted != null)
                    Aborted(this, new RemoteInterfaceAbortedEventArgs(ex));
            }
        }

        /// <summary>
        /// Runs this instance.
        /// </summary>
        /// <remarks></remarks>
        public void Run()
        {
            if (Thread.ThreadState == System.Threading.ThreadState.Running)
                throw new Exception("Thread already running");
            //BeginReceivePacket();
            Thread.Start();
        }

        /// <summary>
        /// Registers the packet.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="id">The id.</param>
        /// <remarks></remarks>
        public void RegisterPacket<T>(byte id) where T : Packet, new()
        {
            EndPoint.RegisterPacket<T>(id);
        }

        /// <summary>
        /// Gets the end point.
        /// </summary>
        /// <remarks></remarks>
        public PacketEndPoint EndPoint { get; private set; }

        /// <summary>
        /// Occurs when [packet received].
        /// </summary>
        /// <remarks></remarks>
        public event EventHandler<PacketEventArgs> PacketReceived;
        /// <summary>
        /// Occurs when [aborted].
        /// </summary>
        /// <remarks></remarks>
        public event EventHandler<RemoteInterfaceAbortedEventArgs> Aborted;

        /// <summary>
        /// Shutdowns this instance.
        /// </summary>
        /// <remarks></remarks>
        public void Shutdown()
        {
            if (!_aborting)
            {
                _aborting = true;
                if (Aborted != null)
                    Aborted(this, new RemoteInterfaceAbortedEventArgs());
            }
            EndPoint.Shutdown();
        }

        #endregion

        /// <summary>
        /// Runs the loop.
        /// </summary>
        /// <remarks></remarks>
        private void RunLoop()
        {
            try
            {
                while (true)
                {
                    if (_aborting) return;
                    ReadPacket();
                }
            }
            catch (ThreadAbortException)
            {
                if (Aborted != null)
                    Aborted(this, new RemoteInterfaceAbortedEventArgs());
            }
            catch (Exception ex)
            {
                if (Aborted != null)
                    Aborted(this, new RemoteInterfaceAbortedEventArgs(ex));
            }
        }

        /// <summary>
        /// Begins the receive packet.
        /// </summary>
        /// <remarks></remarks>
        private void BeginReceivePacket()
        {
            var buff = new byte[1];
            EndPoint.Stream.BeginRead(buff, 0, 1, ReadCompleted, buff);
        }

        /// <summary>
        /// Reads the completed.
        /// </summary>
        /// <param name="a">A.</param>
        /// <remarks></remarks>
        private void ReadCompleted(IAsyncResult a)
        {
            if (a.IsCompleted)
            {
            try
            {
                EndPoint.Stream.EndRead(a);
                Packet p = EndPoint.ReadPacket(((byte[]) a.AsyncState)[0]);
                if (EndPoint.Stream.BufferEnabled)
                    p.Data = EndPoint.Stream.GetBuffer();
                if (PacketReceived != null)
                    PacketReceived(this, new PacketEventArgs(p));
                BeginReceivePacket();
            }
            catch (ThreadAbortException)
            {
                if (Aborted != null)
                    Aborted(this, new RemoteInterfaceAbortedEventArgs());
            }
            catch (Exception ex)
            {
                if (Aborted != null)
                    Aborted(this, new RemoteInterfaceAbortedEventArgs(ex));
            }
            }
        }

        /// <summary>
        /// Reads the packet.
        /// </summary>
        /// <returns></returns>
        /// <remarks></remarks>
        [DebuggerStepThrough]
        public Packet ReadPacket()
        {
            Packet p = EndPoint.ReadPacket();
            if (EndPoint.Stream.BufferEnabled)
                p.Data = EndPoint.Stream.GetBuffer();
            if (PacketReceived != null)
                PacketReceived(this, new PacketEventArgs(p));
            return p;
        }

        /// <summary>
        /// Prepares the end point.
        /// </summary>
        /// <remarks></remarks>
        protected abstract void PrepareEndPoint();

        public void SwitchToRC4Mode(Key key)
        {
            var stream = EndPoint.Stream.Net as FullyReadStream;
            if (stream.BaseStream is RC4Stream)
            {
                var rc4 = stream.BaseStream as RC4Stream;
                rc4.Key = key;
            }
            else
            {
                stream.BaseStream = new RC4Stream(stream.BaseStream, key);
            }
        }
        public void SwitchToHCMode(Key key)
        {
            var stream = EndPoint.Stream.Net as FullyReadStream;
            if (stream.BaseStream is HCStream)
            {
                var hc4 = stream.BaseStream as HCStream;
                hc4.Key = key;
            }
            else
            {
                stream.BaseStream = new HCStream(stream.BaseStream, key);
            }
        }
        public void SwitchToAesMode(Key key)
        {
            var stream = EndPoint.Stream.Net as FullyReadStream;
            if (stream.BaseStream is AesStream)
            {
                var hc4 = stream.BaseStream as AesStream;
                hc4.Key = key;
            }
            else
            {
                stream.BaseStream = new AesStream(stream.BaseStream, key);
            }
        }
    }
}